﻿class unit extends MovieClip{
	private var hp:Number;
	private var speed:Number;
	private var unitName:String;
	private var targetX:Number;
	private var targetY:Number;
	private var targetEntity;		//this can be anything; tower, unit, whatever
	private var aiControl:Boolean;	//this unit is AI controlled
	private var aiState:Number;		//0 = walk, 1 = prepare to attack, 2 = attack
	private var DAM:Number;
	private var DEF:Number;
	private var attackSpeed:Number;	
	private var owner:String;		//left or right; who owns this unit
	private var range:Number;		//range of this unit's attack
	private var parent:player;		//this unit's parent
	private var myManager:manager;	//the main manager for this game
	
	/**
	 * Sets the manager for this unit for callback purposes
	 * @param m the manager to set myManager to	 */
	public function setManager(m:manager):Void{
		myManager = m;
	}
	
	/**
	 * Sets the parent to the specified value
	 * @param p the player to set the parent to	 */
	public function setParent(p:player):Void{
		parent = p;
	}	
	
	//timers
	private var attackSpeedTimer:Number;
	
	/**
	 * Returns the current HP for this unit
	 * @return the current HP of this unit	 */
	public function getHP():Number{
		return hp;	
	}
	
	/**
	 * Returns the name of this unit
	 * @return the name of this unit	 */
	public function getName():String{
		return unitName;
	}

	function unit(){
		aiState = 0;
		targetX = 0;
		targetY = 0;
		targetEntity = null;
		DAM = 10;
		DEF = 1;
		attackSpeed = 1000;
		range = 10;
		aiControl = false;
	}
	
	/**
	 * Initializes unit with specified values
	 * 
	 * @param setName The value to set unitName to
	 * @param setHP The value to set hp to
	 * @param setSpeed The value to set speed to
	 * @param setDAM The value to set DAM to
	 * @param setDEF The value to set DEF to
	 * @param setAttackSpeed The value to set attackSpeed to
	 * @param setRange The value to set range to
	 */
	function initUnit(setName:String, setHP:Number, setSpeed:Number, setDAM:Number, setDEF:Number, setAttackSpeed:Number, setRange:Number):Void{
		unitName	= setName;
		hp 			= setHP;
		speed		= setSpeed;
		DAM			= setDAM;
		DEF			= setDEF;
		attackSpeed	= setAttackSpeed;
		range		= setRange;
	}
	
	function setUnit(h:Number, sp:Number, uName:String, tX:Number, tY:Number, ai:Boolean){
		hp = h;
		speed = sp;
		unitName = uName;
		targetX = tX;
		targetY = tY;
		aiControl = ai;
		
		this.attachMovie("hpBar", "bar", this.getDepth());
		this["bar"].health.text = h;
	}
	
	/**
	 * Sets the AI control to the specified value; true means AI controlled
	 * @param control The new value of aiControl	 */
	public function setAIControl(control:Boolean):Void{
		aiControl = control;	
	}
	
	/**
	 * Sets the value of owner	 */
	public function setOwner(o:String):Void{
		owner = o;	
	}
	
	/**
	 * Returns a clone of this unit
	 * @return returns a clone of this unit	 */
	public function clone():unit{
		var clone:unit = new unit();
		clone.initUnit(unitName, hp, speed, DAM, DEF, attackSpeed, range);
		return clone;
	}
	
	/**
	 * Makes parameter into a copy of this
	 * @param u The unit to transform	 */
	public function duplicate(u:unit):Void{
		u.initUnit(unitName, hp, speed, DAM, DEF, attackSpeed, range);
	}
	
	/**
	 * 'Cleans up' the unit after it has been destroyed
	 * Clears the attack timer interval, notifies the root of its death	 */
	function cleanUp():Void{
		//unit has been killed... clean up
		clearInterval(attackSpeedTimer);
		myManager.unitDeathEvent(this);
		targetEntity = null;
	}
	
	function onEnterFrame(){
		//see if we've hit our target yet
		if(hp <= 0)
			return;
			
		checkTargetReached();
		
		if(targetEntity.hp <= 0){
			acquireTarget();
			aiState = 0;
			gotoAndPlay(1);
			clearInterval(attackSpeedTimer);
		}

		//act depending on AI state
		if(aiControl == true){
			switch(aiState){
				case 0: {//walking to goal
					//gotoAndPlay(1);
					seekTarget();
				} break;
				case 1: {//prepare to attack
					this.gotoAndPlay(5);

					attackSpeedTimer = setInterval(this.takeDamage, attackSpeed, targetEntity, this);
					aiState = 2;
				} break;
				case 2: {//attacking
					//notify our target that we're attacking them
					//if(targetEntity != null)
						//takeDamage(targetEntity);
				} break;
				
				default: break;
			}
		}
	}
	
	/**
	 * This function is called by setInterval, so we need to 
	 * specify ourself, and our target.  'me' is the unit being
	 * attacked; enemy is the unit dealing damage
	 * 
	 * @param me the unit taking damage
	 * @param enemy the unit dealing damage	 */
	function takeDamage(me, enemy):Void{
		//do attack animation
		enemy.gotoAndPlay("attack");

		me.hp -= enemy.DAM;
		me["bar"].health.text = me.hp;
		
		if(me.hp <= 0){ //if unit has been dealt fatal damage, clean it up!
			me.cleanUp();
			me.gotoAndPlay("death");
		}
	}
	
	/**
	 * Units cycles through all units and targets the closest appropriate one
	 * TODO More work to be done here...	 */
	function acquireTarget():Void{
		//acquires target if it doesn't have one
		//goes through list of all units and finds the closest enemy unit
		//TODO different units might have preferences...
		//ground shouldn't acquire flying, etc...
		//how is a tower acquired?  maybe if there are no more units...
		//also we should not go backwards to acquire targets either...
		//it should also be the CLOSEST target, not a further one
		var bestDistance:Number = 9999;
		var unitEntities:Array = myManager.getUnits();

		for(var i = 0; i < unitEntities.length; i++){
			if(unitEntities[i] != this && isEnemy(unitEntities[i]) && getYDistance(this, unitEntities[i]) <= range){
				if(Math.abs(unitEntities[i]._x - this._x) < bestDistance){
					targetEntity = unitEntities[i];
					bestDistance = Math.abs(targetEntity._x - this._x);
				}
			}
		}
		//if we can't find a suitable target, track the tower
		if(bestDistance == 9999){
			targetEntity = myManager.getEnemyTower(parent).getBase();
		}
			
		faceTarget();
	}
	
	/**
	 * Causes this unit to face its current target	 */
	private function faceTarget():Void{
		//orient this unit correctly; assume it is drawn facing right
		if(this._x > targetEntity._x){
			this._xscale = -100;
		}
		else if (this._x < targetEntity._x)
			this._xscale = 100;
	}
	
	/**
	 * Gets the difference between unit and its target
	 * @return the distance between the unit and its target	 */
	private function getDistance(me:MovieClip, target:MovieClip):Number{
		return Math.sqrt((me._x - target._x) * (me._x - target._x) + (me._y - target._y) * (me._y - target._y));
	}
	
	/**
	 * Gets the vertical difference between the unit and its target
	 * @return the vertical distance between the unit and its target	 */
	private function getYDistance(me:MovieClip, target:MovieClip):Number{
		//returns difference from target on the y-axis
		return Math.abs(me._y - target._y);
	}
	
	/**
	 * Compares owner of unit and u
	 * @return true if this.owner and u.owner are the same; false otherwise	 */
	private function isEnemy(u:unit):Boolean{
		//returns true if u is an enemy and false otherwise
		if(owner == u.owner)
			return false;
		else  if (owner !=  null && owner != u.owner)
			return true;
		else
			trace("Error in isEnemy()");
	}
	
	/**
	 * Causes this unit to move towards its current target	 */
	function seekTarget():Void{
		//moves this unit towards its target
		faceTarget();
		
		if(within(this._x, targetEntity._x, speed)){ aiState = 1; }
		else if(this._x < targetEntity._x)
			this._x += speed;
		else if (this._x > targetEntity._x)
			this._x -= speed;

		/*if(within(this._y, targetEntity._y, speed)){ }
		else if(this._y < targetEntity._y)
			this._y += speed;
		else if (this._y > targetEntity._y)
			this._y -= speed;*/
	}
	
	/**
	 * Checks to see if this unit has reached its target
	 * Sets AI state to attack if it has	 */
	function checkTargetReached():Void{
		if(this.hitTest(targetEntity) && aiState == 0){
			aiState = 1;
		}
	}
	
	/**
	 * Returns true if n is within delta of target
	 * @return true if n is within delta of target; false otherwise	 */
	private function within(n:Number, target:Number, delta:Number):Boolean{
		//returns true if n is within delta of target; false otherwise
		if(n < target + delta and n > target - delta)
			return true;
		return false;
	}
}